<?php
// $Id: taxonomy_export.pages.inc,v 1.1.2.4 2010/12/05 21:54:09 jamesandres Exp $

/**
 * @file taxonomy_export.pages.inc
 *  Menu callbacks for taxonomy_export.
 **/

/**
 * FAPI callback: Displays a form allowing the admin to choose which
 * vocabulary to export.
 **/
function taxonomy_export_export($form_state) {
  $form = array(
    '#redirect' => FALSE
  );

  $vocabularies = taxonomy_get_vocabularies();
  $options = array();
  foreach ($vocabularies as $vid => $vocabulary) {
    $options[$vid] = $vocabulary->name;
  }

  if ($form_state['storage']['taxonomy_export']) {
    $form['back'] = array(
      '#value' => l(t('<< Back, export more vocabularies'), 'admin/content/taxonomy/export'),
    );
    $form['taxonomy_export'] = array(
      '#type' => 'textarea',
      '#title' => t('Export data'),
      '#cols' => 80,
      '#rows' => 30,
      '#default_value' => $form_state['storage']['taxonomy_export'],
      '#attributes' => array('readonly' => 'readonly'),
    );
  }
  else {
    $form['vid'] = array(
      '#type' => 'radios',
      '#title' => t('Vocabulary to export'),
      '#default_value' => isset($form_state['values']['vid']) ? $form_state['values']['vid'] : key($options),
      '#options' => $options,
    );
    $form['include_terms'] = array(
      '#type' => 'checkbox',
      '#title' => t('Include Terms?'),
      '#default_value' => isset($form_state['values']['include_terms']) ? $form_state['values']['include_terms'] : FALSE,
      '#description' => t('By default only the vocabulary definition is exported, not the term data also.  <em>Warning, if you have a lot of terms this may generate a large export!</em>')
    );
    $form['as_file'] = array(
      '#type' => 'checkbox',
      '#title' => t('Save export as file?'),
      '#default_value' => isset($form_state['values']['as_file']) ? $form_state['values']['as_file'] : FALSE,
      '#description' => t('By default, exports are displayed in a text area for copying to the clipboard.')
    );
    $form['export'] = array(
      '#type' => 'submit',
      '#value' => t('Export'),
    );
  }

  return $form;
}

/**
 * FAPI submit handler for taxonomy_export_export().
 **/
function taxonomy_export_export_submit($form, &$form_state) {
  $export = array();

  $vocabulary = taxonomy_vocabulary_load($form_state['values']['vid']);
  // Add Translation mode for vocabulary if Multilingual options enabled for taxonomy.
  if (module_exists('i18ntaxonomy')) {
    $vocabulary->i18nmode = i18ntaxonomy_vocabulary($form_state['values']['vid']);
  }
  $munged_vocabulary_name = strtolower(preg_replace('/[^A-Za-z0-9-]/', '_', $vocabulary->name));

  if ($form_state['values']['include_terms']) {
    $terms = taxonomy_get_tree($vocabulary->vid);
  }

  $export  = "<?php\n";
  $export .= "// Generated by taxonomy_export " . TAXONOMY_EXPORT_VERSION . "\n\n";
  $export .= '$taxonomy_export = ';
  $export .= strtr(var_export(array('vocabulary' => $vocabulary, 'terms' => $terms,), 1), array('stdClass::__set_state' => '(object) '));
  $export .= ";\n";

  if ($form_state['values']['as_file']) {
    // Set headers to make this a file attachment download
    drupal_set_header('Content-type: text/plain');
    drupal_set_header('Content-Disposition: attachment; filename="' . $munged_vocabulary_name . '.taxonomy_export.inc"');

    // Print the export, and immediately exit
    echo $export;
    module_invoke_all('exit');
    exit;
  }
  else {
    $form_state['storage']['taxonomy_export'] = $export;
  }
}

/**
 * FAPI callback: Displays a form allowing the admin to import vocabulary
 * definitions.
 **/
function taxonomy_export_import($form_state) {
  $form = array();

  $result = db_query("SELECT vid, name FROM {vocabulary} ORDER BY weight, name");
  $options = array();
  while ($vocabulary = db_fetch_object($result)) {
    $options[$vocabulary->vid] = $vocabulary->name;
  }
  $form['update'] = array(
    '#type' => 'select',
    '#title' => t('Vocabulary'),
    '#default_value' => '',
    '#options' => array('' => t('<Create>')) + $options,
    '#description' => t('Select the vocabulary to import this taxonomy data into.  Choose "<Create>" to import into a new vocabulary.'),
  );
  $form['import_data'] = array(
    '#type' => 'textarea',
    '#title' => t('Taxonomy import data'),
    '#cols' => 80,
    '#rows' => 30,
    '#default_value' => isset($form_state['values']['import']) ? $form_state['values']['import'] : '',
    '#description' => t('Paste string from a Taxonomy Export dump.'),
    '#required' => TRUE,
  );
  $form['import'] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );

  return $form;
}

/**
 * FAPI validation handler for taxonomy_export_import().
 **/
function taxonomy_export_import_validate($form, &$form_state) {
  if (_taxonomy_export_prepare_import_data($form_state['values']['import_data']) == FALSE) {
    form_set_error('import_data', t('Could not parse import data.'));
  }
}

/**
 * Strips leading and trailing comments, <?php tag, etc. off of
 * the import_data.
 **/
function _taxonomy_export_prepare_import_data(&$import_data) {
  $matches = array();

  preg_match('/(\$taxonomy_export.*\));/s', $import_data, $matches);

  if ($matches && $matches[1]) {
    $import_data = $matches[1];
    return TRUE;
  }

  return FALSE;
}

/**
 * FAPI submit handler for taxonomy_export_import().
 **/
function taxonomy_export_import_submit($form, &$form_state) {
  // Suppress errors incase the import fails.  The eval should instantiate the
  // $taxonomy_export variable, if everything happens correctly.
  @eval('return ' . $form_state['values']['import_data'] . ';');

  if (!isset($taxonomy_export) || !$taxonomy_export || !isset($taxonomy_export['vocabulary'])) {
    drupal_set_message(t("Error parsing import data."), 'error');
    return;
  }

  $vocabulary = (array) $taxonomy_export['vocabulary'];
  if ($vocabulary && $vocabulary['name']) {
    $vid = $form_state['values']['update'];
    // Insert a vocabulary, or attempt to update an existing one
    $status = _taxonomy_export_import_vocabulary($vocabulary, $vid);
  }

  if ($taxonomy_export['terms']) {
    // Clear out terms that have been removed.
    $removed_terms = _taxonomy_export_prune_terms($vocabulary, $taxonomy_export['terms']);

    // Parent map keeps track of existing TID hierarchy, allowing it to
    // be recreated.
    $parent_tid_map = array();

    // Pass #1, create all the terms
    foreach ($taxonomy_export['terms'] as $key => $term) {
      $term_ref =& $taxonomy_export['terms'][$key];
      $parent_tid_map[$term->tid] = $key;

      $term_array = (array) $term;
      // We don't reset the TID as this may be required by
      // _taxonomy_export_import_term()
      $term_array['vid'] = $vocabulary['vid'];
      $term_array['parent'] = array(0 => 0);
      _taxonomy_export_import_term($term_array);

      // Update the original $taxonomy_export structure with the new TID,
      // VID, and parent attributes.
      $term_ref->tid = $term_array['tid'];
      $term_ref->vid = $term_array['vid'];

      // taxonomy_get_tree() returns the hierarchy in a "parents" attribute
      // while taxonomy_save_term() requires a "parent" attribute ...
      $term_ref->parent = $taxonomy_export['terms'][$key]->parents;
    }

    // Pass #2, rebuild the term hierarchy
    foreach ($taxonomy_export['terms'] as $key => $term) {
      if ($term->parent[0] == 0) {
        continue;
      }

      foreach ($term->parent as $pkey => $ptid) {
        // The parent TIDs are still relative to the old imported TIDs, use
        // the $parent_tid_map created earlier to lookup the new TID for this
        // parent.
        $term->parent[$pkey] = $taxonomy_export['terms'][$parent_tid_map[$ptid]]->tid;
      }

      $term_array = (array) $term;
      taxonomy_save_term($term_array);
    }

    $num = count($parent_tid_map);
    if ($status == SAVED_NEW) {
      drupal_set_message(t("Created vocabulary %name, %num terms imported successfully.", array('%name' => $vocabulary['name'], '%num' => $num)));
    }
    else {
      $num_removed = count($removed_terms);
      drupal_set_message(t("Updated vocabulary %name, %num terms updated and %rem removed.", array('%name' => $vocabulary['name'], '%num' => $num, '%rem' => $num_removed)));
    }
  }
}

/**
* Allows vocabularies to be created with an explicit vid.
*
* @param $vocabulary
*   A keyed array defining a vocabulary term.
*
* @return
*   SAVED_UPDATED (2), or FALSE on failure.
*/
function _taxonomy_export_import_vocabulary(&$vocabulary, $vid = NULL) {
  $existing_vocabulary = db_fetch_array(db_query("SELECT * FROM {vocabulary} WHERE vid = %d", $vid));

  // Install a new vocabulary if an update was not requested, no vocabulary was
  // found that could be updated.
  if (!is_numeric($vid) || !$existing_vocabulary) {
    $vocabulary['vid'] = NULL;
  }
  else if (is_numeric($vid) && !$existing_vocabulary) {
    drupal_set_message(t("Error, could not find the vocabulary to update in the database.  Please try again."), 'error');
  }

  $status = taxonomy_save_vocabulary($vocabulary);

  return $status;
}

/**
* Allows terms to be updated and/or created as necessary.
*/
function _taxonomy_export_import_term(&$term_array) {
  $tid = $term_array['tid'];
  $vid = $term_array['vid'];

  $term = db_fetch_array(db_query("SELECT * FROM {term_data} WHERE tid = %d AND vid = %d", $tid, $vid));
  $term_array['tid'] = ($term && $term['tid']) ? $term_array['tid'] : NULL;

  $status = taxonomy_save_term($term_array);

  return $status;
}

/**
 * During a vocabulary update, prune orphaned terms from the database.  Terms
 * that exist in the database but are not present in the export.
 *
 * @param $vocabulary
 *  The vocabulary to re pruned.
 * @param $import_terms
 *  Terms array from the taxonomy export.
 */
function _taxonomy_export_prune_terms($vocabulary, $import_terms) {
  // Get a list of terms that are currently in the db for this vocabulary.
  $current_terms = array();
  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vocabulary['vid']);
  while ($tid = db_result($result)) {
    $current_terms[] = $tid;
  }

  if (!count($current_terms)) {
    return array();
  }

  // Get a list of tids from the new terms
  foreach ($import_terms as $new_term) {
    $new_terms[] = $new_term->tid;
  }

  // Get the non-overlapping old terms and remove them.
  $removed_terms = array_diff($current_terms, $new_terms);

  foreach ($removed_terms as $key => $tid) {
    // Set the name of each removed term, for a more friendly message below.
    $removed_terms[$key] = db_result(db_query("SELECT name FROM {term_data} WHERE tid = %d", $tid));
    taxonomy_del_term($tid);
  }

  return $removed_terms;
}

